#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>	/* for sprintf() */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>	/* for get_user and put_user */
#include <linux/version.h>
#include <linux/delay.h>

#include <asm/errno.h>

 /*  Prototypes - this would normally go in a .h file */
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char __user *, size_t,
			    loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev"	/* Dev name as it appears in /proc/devices   */

static int major;		/* major number assigned to our device driver */
static DEFINE_MUTEX(mymutex);
static struct class *cls;

static int device_open(struct inode *inode, struct file *file)
{
	static int counter = 0;
	int ret;
	ret = mutex_trylock(&mymutex);
	if (ret == 0) {
		pr_err("device has open\n");
		return -EBUSY;
	}

	pr_info("device open %d\n", counter++);
	try_module_get(THIS_MODULE);

	return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
	mutex_unlock(&mymutex);
	module_put(THIS_MODULE);
	pr_info("release\n");

	return SUCCESS;
}

static unsigned char data[64];

static ssize_t device_read(struct file *filp,
			   char __user * buffer, size_t length, loff_t * off)
{
	int ret = 0;
	int remain_len = sizeof(data) - *off;
	int len;
	if (remain_len <= 0) {
		pr_info("read end\n");
		return 0;
	}

	len = remain_len;
	if (length < remain_len)
		len = length;

	ret = copy_to_user(buffer, data, len);
	if (ret == 0) {
		ret = len;
		*off += len;
	}
	msleep(30 * 1000);

	return ret;
}

static ssize_t device_write(struct file *filp, const char __user * buff,
			    size_t length, loff_t * off)
{
	int ret = 0;
	int len = sizeof(data);
	if (*off > 0) {
		// 只支持一次读取, 不支持流式读写
		pr_err
		    ("not support stream read/write; or write to twice / more\n");
		return -1;
	}

	if (length < len)
		len = length;

	ret = copy_from_user(data, buff, len);
	if (ret == 0) {
		ret = len;
		*off = len;
	}
	msleep(30 * 1000);

	return ret;
}

static struct file_operations chardev_fops = {
	.read = device_read,
	.write = device_write,
	.open = device_open,
	.release = device_release,
};

static int __init chardev_init(void)
{
	major = register_chrdev(0, DEVICE_NAME, &chardev_fops);

	if (major < 0) {
		pr_alert("Registering char device failed with %d\n", major);
		return major;
	}

	pr_info("I was assigned major number %d.\n", major);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
	cls = class_create(DEVICE_NAME);
#else
	cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
	device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);

	pr_info("Device created on /dev/%s\n", DEVICE_NAME);

	return SUCCESS;
}

static void __exit chardev_exit(void)
{
	device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, DEVICE_NAME);
	pr_info("devvice exit\n");
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_LICENSE("GPL");
